﻿using System;
using System.Collections.Generic;
using System.Linq;
using BlockLogic.Impl.Blocks.Checker.Enum;
using BlockLogic.Interface.Blocks.Checker.Strategy;

namespace BlockLogic.Impl.Blocks.Checker.Strategy
{
    public abstract class CheckingStrategy : ICheckerStrategy
    {
        private List<double> _data;
        protected CheckerType _type;

        public List<double> Data 
        { 
            set 
            {
                _data.Clear();
                _data.AddRange(value);
                _data.Sort();
            } 
        }

        public int IntervalCount
        {
            get
            {
                return (_data.Count < 100 ? (int)Math.Sqrt(_data.Count) : (int)(Math.Log(_data.Count) * 4));
            }
        }

        public CheckingStrategy()
        {
            _data = new List<double>();
        }

        private int GetS()
        {
            switch (_type)
            {
                case CheckerType.UNIFORM:
                    return 2;
                case CheckerType.EXPONENTIAL:
                    return 1;
                case CheckerType.GAUSSIAN:
                    return 2;
                default:
                    throw new ArgumentException();
            }
        }

        public int DegreesOfFreedom()
        {
            return IntervalCount - 1 - GetS();
        }

        private void EqualProbabilityFragmentation(double[] left, double[] right)
        {
            int m = IntervalCount;
            int v = _data.Count / m;

            left[0] = _data.Min();
            var h = new double[m];
            for (int i = 0; i < m - 1; i++)
            {
                right[i] = (_data[(i + 1) * v] + _data[(i + 1) * v + 1]) / 2;
                h[i] = right[i] - left[i];
                left[i + 1] = right[i];
            }
            right[m - 1] = _data.Max();
        }

        public double PearsonCriterion()
        {
            var m = IntervalCount;
            var n = _data.Count;
            double v = (double) n / m;

            var left = new double[m];
            var right = new double[m];
            
            EqualProbabilityFragmentation(left, right);

            var h = new double[m];
            for (int i = 0; i < m; i++)
            {
                h[i] = right[i] - left[i];
            }

            double criterion = 0;
            for (int i = 0; i < m; i++)
            {
                var prob = TheoreticProbability(left[i], right[i]);
                var f = v / n;
                criterion += n * Math.Pow(prob - f, 2) / f;
            }

            return criterion;
        }

        protected abstract double DistributionFunctionValue(double x);

        protected virtual double TheoreticProbability(double left, double right)
        {
            return DistributionFunctionValue(right) - DistributionFunctionValue(left);
        }

        public IEnumerable<KeyValuePair<double, double>> LinearHistogram()
        {
            var v = _data.Count / IntervalCount;
            var f = new double[IntervalCount];
            var left = new double[IntervalCount];
            var right = new double[IntervalCount];

            EqualProbabilityFragmentation(left, right);
            for (int i = 0; i < IntervalCount; i++)
            {
                f[i] = v / (_data.Count * (right[i] - left[i]));
            }

            return GetLinearHistogram(left, right, f);
        }

        private IEnumerable<KeyValuePair<double, double>> GetLinearHistogram(double[] left, double[] right, double[] f)
        {
            var points = new List<KeyValuePair<double, double>>();
            KeyValuePair<double, double> pair;

            for (int i = 0; i < left.Length; i++)
            {
                //pair = new KeyValuePair<double, double>((left[i] + right[i]) / 2, f[i]);
                //points.Add(pair);
                pair = new KeyValuePair<double, double>(left[i] + Math.Pow(10, -9), f[i]);
                points.Add(pair);
                pair = new KeyValuePair<double, double>(right[i] - Math.Pow(10, -9), f[i]);
                points.Add(pair);
            }

            return points;
        }

        public IEnumerable<KeyValuePair<double, double>> TheoreticalHistogram()
        {
            var v = _data.Count / IntervalCount;
            var f = new double[IntervalCount];
            var left = new double[IntervalCount];
            var right = new double[IntervalCount];

            EqualProbabilityFragmentation(left, right);

            for (int i = 0; i < IntervalCount; i++)
            {
                f[i] =  TheoreticProbability(left[i],right[i]) / (right[i] - left[i]);
            }

            return GetTheoreticalFunctionHistogram(left, right, f);
        }

        private IEnumerable<KeyValuePair<double, double>> GetTheoreticalFunctionHistogram(double[] left, double[] right, double[] f)
        {
            var points = new List<KeyValuePair<double, double>>();
            KeyValuePair<double, double> pair;

            for (int i = 0; i < left.Length; i++)
            {
                pair = new KeyValuePair<double, double>(left[i] + Math.Pow(10, -9), f[i]);
                points.Add(pair);
                pair = new KeyValuePair<double, double>(right[i] - Math.Pow(10, -9), f[i]);
                points.Add(pair);
            }

            return points;
        }


    }
}
